package com.dave.gd.core.common.globalsession;

import java.io.Serializable;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dave.gd.common.redis.impl.CacheServiceImpl;

;

@SuppressWarnings({"deprecation", "unchecked" })
public class GlobalHttpSession implements HttpSession {
    
    private static final String INVALIDATED_TRUE  = "true";         // 不合法
    private static final String INVALIDATED_FALSE = "false";        // 合法
    private static final String METADATA_KEY      = "__metadata__";
    
    public static class Metadata implements Serializable {
        
        /**
         * 
         */
        private static final long serialVersionUID = 2026919021321878024L;
        private String            invalidated;
        private String            creationTime;
        private String            lastAccessedTime;
        
        public Metadata() {
        }
        
        public String getInvalidated() {
            return invalidated;
        }
        
        public void setInvalidated(String invalidated) {
            this.invalidated = invalidated;
        }
        
        public String getCreationTime() {
            return creationTime;
        }
        
        public void setCreationTime(String creationTime) {
            this.creationTime = creationTime;
        }
        
        public String getLastAccessedTime() {
            return lastAccessedTime;
        }
        
        public void setLastAccessedTime(String lastAccessedTime) {
            this.lastAccessedTime = lastAccessedTime;
        }
        
        // 将元数据转成Map
        public Map<String, String> toMap() {
            Map<String, String> map = new HashMap<String, String>();
            map.put("invalidated", getInvalidated());
            map.put("creationTime", getCreationTime());
            map.put("lastAccessedTime", getLastAccessedTime());
            return map;
        }
        
        // 将Map转成元数据
        public static Metadata toMetaData(Map<String, String> map) {
            if (map == null)
                return null;
            if (map.size() == 0)
                return null;
            
            Metadata md = new Metadata();
            md.setInvalidated(map.get("invalidated"));
            md.setCreationTime(map.get("creationTime"));
            md.setLastAccessedTime(map.get("lastAccessedTime"));
            
            return md;
        }
        
        @Override
        public String toString() {
            return "com.ideal.globalsession.GlobalHttpSession$Metadata(invalidated: "
                + getInvalidated() + ", creationTime: " + getCreationTime()
                + ", lastAccessedTime: " + getLastAccessedTime() + ")";
        }
        
    }
    
    private static final Logger               log                        = LoggerFactory
                                                                             .getLogger(GlobalHttpSession.class);
    
    // should be Serializable type(应该是可序列化类型)
    private ConcurrentHashMap<String, String> attributes                 = new ConcurrentHashMap<String, String>();
    private Metadata                          metadata;
    
    private final String                      sessionId;
    // private final SessionStore store;// session的存储方式
    
    private CacheServiceImpl                  cacheService;
    
    /**
     * 当需要为用户端建立一个session时，servlet容器就创建了一个HttpSession对象。其中存储了和本session相关的信息。
     * 所以，在一个servlet中有多少个不同用户连接，就会有多少个HttpSession对象
     */
    private final HttpSession                 session;
    private final StoreKeyGenerator           keyGenerator;
    
    private boolean                           isNewlyCreated             = false;
    private Integer                           maxInactiveIntervalSeconds = null;
    
    public StoreKeyGenerator getKeyGenerator() {
        return keyGenerator;
    }
    
    /**
     * 验证session是否合法(metadata中invalidated是否合法及数据是否过期)
     * 
     * @return
     */
    public boolean isValid() {
        
        Map<String, String> metadataMap = cacheService.hgetAll(keyGenerator.generate(METADATA_KEY));
        boolean isNotInvalidated = metadata != null && metadata.getInvalidated() != null
            && INVALIDATED_FALSE.equals(metadata.getInvalidated());// metadata中invalidated是否合法
        
        boolean isNotExpired = metadataMap != null; // 是否过期
        
        if (log.isDebugEnabled()) {
            log.debug("isValid is called. (isNotInvalidated: " + isNotInvalidated
                + ", isNotExpired: " + isNotExpired + ")");
        }
        
        return isNotInvalidated && isNotExpired;
    }
    
    /**
     * 从数据库中重新加载存储的attributes
     */
    public synchronized void reloadAttributes() {
        attributes.clear();
        
        Map<String, String> cachedAttributes = cacheService.hgetAll(keyGenerator
            .generate(METADATA_KEY));
        if (cachedAttributes != null) {
            attributes.putAll(cachedAttributes);
        }
    }
    
    public ConcurrentHashMap<String, String> getAttributes() {
        return attributes;
    }
    
    /**
     * 将attributes和metadata存入数据库
     */
    public void save() {
        if (isValid()) {
            saveAttributesToStore();
            if (!isNewlyCreated) {
                metadata.setLastAccessedTime(String.valueOf(System.currentTimeMillis()));
            }
            cacheService.hmset(keyGenerator.generate(METADATA_KEY), metadata.toMap());
        } else {
            removeAttributesFromStore();
        }
    }
    
    /**
     * 创建GlobalHttpSession,在GlobalSessionHttpRequest的构造方法中调用此方法
     * 
     * @param sessionId
     * @param namespace
     * @param timeoutMinutes
     * @param session
     */
    
    public GlobalHttpSession(String sessionId, String namespace, Integer timeoutMinutes,
        HttpSession session) {
        
        if (sessionId == null || sessionId.trim().length() == 0) {
            throw new IllegalArgumentException("sessionId should not be empty.");
        }
        if (namespace == null || namespace.trim().length() == 0) {
            throw new IllegalArgumentException("namespace should not be empty.");
        }
        if (timeoutMinutes == null) {
            throw new IllegalArgumentException("timeoutMinutes should not be empty.");
        }
        if (session == null) {
            throw new IllegalArgumentException("session should not be empty.");
        }
        
        this.sessionId = sessionId;
        this.session = session;
        this.keyGenerator = new StoreKeyGenerator(sessionId, namespace);
        
        setMaxInactiveInterval(timeoutMinutes * 60);
        
        Map<String, String> map = cacheService.hgetAll(keyGenerator.generate(METADATA_KEY));
        metadata = Metadata.toMetaData(map);
        if (metadata == null) {
            isNewlyCreated = true;
            metadata = new Metadata();
            metadata.setInvalidated(INVALIDATED_FALSE);
            metadata.setCreationTime(String.valueOf(System.currentTimeMillis()));
            /**
             * 下面这句代码必须要加上,否则数据无法插入Jedis,会报JedisDataException(value sent to
             * redis cannot be null)
             */
            metadata.setLastAccessedTime(String.valueOf(System.currentTimeMillis()));
            cacheService.hmset(keyGenerator.generate(METADATA_KEY), metadata.toMap());
        }
        reloadAttributes();
        
        if (log.isDebugEnabled()) {
            log.debug("A new GlobalHttpSession is created. (sessionId: " + sessionId
                + ", attributes: " + attributes + ")");
        }
    }
    
    @Override
    public Object getAttribute(String name) {
        
        Object value = null;
        if (isValid()) {
            value = attributes.get(name);
        } else {
            value = null;
        }
        
        if (log.isDebugEnabled()) {
            log.debug("getAttribute is called. (sessionId: " + sessionId + ", " + name + " -> "
                + value + ")");
        }
        
        return value;
    }
    
    @Override
    public Enumeration<?> getAttributeNames() {
        if (isValid()) {
            final Iterator<String> names = attributes.keySet().iterator();
            return new Enumeration<Object>() {
                
                public boolean hasMoreElements() {
                    return names.hasNext();
                }
                
                public Object nextElement() {
                    return names.next();
                }
            };
        } else {
            return new Enumeration<Object>() {
                
                public boolean hasMoreElements() {
                    return false;
                }
                
                public Object nextElement() {
                    return null;
                }
            };
        }
    }
    
    @Override
    public void invalidate() {
        
        if (log.isDebugEnabled()) {
            log.debug("invalidate is called. (sessionId: " + sessionId + ")");
        }
        
        session.invalidate();
        attributes.clear();
        metadata.setInvalidated(INVALIDATED_TRUE);
        removeAttributesFromStore();
    }
    
    @Override
    public void removeAttribute(String name) {
        reloadAttributes();
        attributes.remove(name);
        saveAttributesToStore();
    }
    
    @Override
    public void setAttribute(String name, Object value) {
        
        if (log.isDebugEnabled()) {
            log.debug("setAttribute is called. (sessionId: " + sessionId + ", " + name + " -> "
                + value + ")");
        }
        
        if (value == null) {
            removeAttribute(name);
        }
        if (value instanceof Serializable) {
            reloadAttributes();
            attributes.put(name, value.toString());
            saveAttributesToStore();
        } else {
            String message = "The value should be an instance of java.io.Serializable. (" + value
                + ")";
            throw new IllegalArgumentException(message);
        }
    }
    
    @Override
    public Object getValue(String name) {
        return getAttribute(name);
    }
    
    @Override
    public String[] getValueNames() {
        Enumeration<String> names = (Enumeration<String>) getAttributeNames();
        return Collections.list(names).toArray(new String[] {});
    }
    
    @Override
    public String getId() {
        // return sessionId;
        return keyGenerator.generate(METADATA_KEY);
    }
    
    @Override
    public long getCreationTime() {
        if (metadata == null || metadata.getCreationTime() == null) {
            return 0L;
        } else {
            return Long.parseLong(metadata.getCreationTime());
        }
    }
    
    @Override
    public long getLastAccessedTime() {
        if (metadata == null || metadata.getLastAccessedTime() == null) {
            return 0L;
        } else {
            return Long.parseLong(metadata.getLastAccessedTime());
        }
    }
    
    @Override
    public int getMaxInactiveInterval() {
        return this.maxInactiveIntervalSeconds;
    }
    
    @Override
    public ServletContext getServletContext() {
        return session.getServletContext();
    }
    
    @Override
    public HttpSessionContext getSessionContext() {
        // We don't support HttpSessionContext as a part of GlobalHttpSession
        // because it's already deprecated API.
        return session.getSessionContext();
    }
    
    @Override
    public boolean isNew() {
        return isNewlyCreated;
    }
    
    @Override
    public void putValue(String name, Object value) {
        setAttribute(name, value);
    }
    
    @Override
    public void removeValue(String name) {
        removeAttribute(name);
    }
    
    @Override
    public void setMaxInactiveInterval(int interval) {
        this.maxInactiveIntervalSeconds = interval;
        session.setMaxInactiveInterval(interval);
    }
    
    private void saveAttributesToStore() {
        cacheService.hmset(keyGenerator.generate(METADATA_KEY), getAttributes());
    }
    
    private void removeAttributesFromStore() {
        cacheService.del(keyGenerator.generate(METADATA_KEY));
    }
    
    @Override
    public String toString() {
        StringBuilder attributes = new StringBuilder();
        for (Object attr : Collections.list(getAttributeNames())) {
            attributes.append(attr);
            attributes.append(",");
        }
        return "com.ideal.globalsession.GlobalHttpSession(id: " + getId() + ", attributes: ["
            + attributes.toString().replaceFirst(",$", "") + "], creationTime: "
            + getCreationTime() + ", lastAccessedTime: " + getLastAccessedTime()
            + ", maxInactiveInterval: " + getMaxInactiveInterval() + ")";
    }
}
